﻿/********************************************************
 *  ██████╗  ██████╗████████╗██╗
 * ██╔════╝ ██╔════╝╚══██╔══╝██║
 * ██║  ███╗██║        ██║   ██║
 * ██║   ██║██║        ██║   ██║
 * ╚██████╔╝╚██████╗   ██║   ███████╗
 *  ╚═════╝  ╚═════╝   ╚═╝   ╚══════╝
 * Geophysical Computational Tools & Library (GCTL)
 *
 * Copyright (c) 2023  Yi Zhang (yizhang-geo@zju.edu.cn)
 *
 * GCTL is distributed under a dual licensing scheme. You can redistribute 
 * it and/or modify it under the terms of the GNU Lesser General Public 
 * License as published by the Free Software Foundation, either version 2 
 * of the License, or (at your option) any later version. You should have 
 * received a copy of the GNU Lesser General Public License along with this 
 * program. If not, see <http://www.gnu.org/licenses/>.
 * 
 * If the terms and conditions of the LGPL v.2. would prevent you from using 
 * the GCTL, please consider the option to obtain a commercial license for a 
 * fee. These licenses are offered by the GCTL's original author. As a rule, 
 * licenses are provided "as-is", unlimited in time for a one time fee. Please 
 * send corresponding requests to: yizhang-geo@zju.edu.cn. Please do not forget 
 * to include some description of your company and the realm of its activities. 
 * Also add information on how to contact you by electronic and paper mail.
 ******************************************************/

#include "getopt_help.h"


/**
 * @brief      Display a string massage using the standard outputs.
 *
 * @param[in]  f_space     The front space before the printing.
 * @param[in]  b_space     The end space before the printing.
 * @param[in]  hang_space  The hang space starting from the second line.
 * @param[in]  wth          window size of the terminal
 * @param[in]  msg         The message
 * @param[in]  stdout         Output stream. The default is the standard error output
 */
void display_line(int f_space, int b_space, int hang_space, int wth, std::string msg, std::ostream& sout=std::clog)
{
	int line_length = f_space + b_space;
	std::string segment;
	std::stringstream ss_message;

	ss_message.clear();
	ss_message.str(msg);
	while (ss_message >> segment)
	{
		if ((line_length+segment.length()) <= wth)
		{
			if (line_length == f_space+b_space)
			{
				for (int i = 0; i < f_space; i++)
					sout << " ";
				sout << segment << " ";
				line_length += segment.length()+1;
			}
			else
			{
				sout << segment << " ";
				line_length += segment.length()+1;
			}
		}
		else
		{
			sout << std::endl;
			for (int i = 0; i < f_space+hang_space; i++)
				sout << " ";
			sout << segment << " ";
			line_length = (segment.length()+1+hang_space+f_space+b_space);
		}
	}
	sout << std::endl;
	return;
}

/**
 * @brief      display formatted help information in terminal
 * 
 * This function will display formated help information in the terminal using
 * the standard error output. We choose the error output to avoid unexpected
 * redirection and make sure the information will appear in the terminal.
 *
 * @param[in]  proname       The program's name.
 * @param[in]  brief         The brief information show right after the program's name.
 * @param[in]  exp_longopts  The pointer of expanded longopts
 */
void gctl::getopt_long_help(const option *longopts, const option_info *expd_longopts, const char* proname, 
	const char* brief, std::ostream& sout, std::string extra_usage)
{
	// Default layout. One can only change this by recompile the library.
	int front_space = 0, back_space = 10;

	int line_length;
	std::string m1, m2, m3;
	std::string segment;
	std::stringstream ss_message;

	int width;
	//获取终端窗口的行列数
#ifdef _WINDOWS
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
	width = csbi.srWindow.Right - csbi.srWindow.Left;
#else
	struct winsize w;
	ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
	width = w.ws_col;
#endif
	

	// We firstly display the program's name which is assumed to be the first argument.
	m1 = proname;
	m1 += " - ";
	m2 = brief;
	m1 += m2;
	display_line(front_space, back_space, 4, width, m1, sout);
	sout << std::endl;

	// display usage
	int loop_index = 0;
	m2 = proname;
	m1 = "Usage: " + m2;
	while(1)
	{
		if (longopts[loop_index].name != 0 && expd_longopts[loop_index].format != 0)
		{
			if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122)
				m2 = (char)longopts[loop_index].val;
			else
			{
				m2 = "-";
				m2 += longopts[loop_index].name;
			}

			m3 = expd_longopts[loop_index].format;
			if (!expd_longopts[loop_index].manda)
				m1 += " [-" + m2 + m3 +"]";
			else
			{
#ifdef _WINDOWS
				m1 += " -" + m2 + m3;
#else
				m1 += GCTL_BOLD;
				m1 += " -" + m2 + m3;
				m1 += GCTL_RESET;
#endif
			}
			loop_index++;
		}
		else if (longopts[loop_index].name != 0)
		{
			if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122)
				m2 = (char)longopts[loop_index].val;
			else
			{
				m2 = "-";
				m2 += longopts[loop_index].name;
			}

			if (!expd_longopts[loop_index].manda)
				m1 += " [-" + m2 + "]";
			else
			{
#ifdef _WINDOWS
				m1 += " -" + m2;
#else
				m1 += GCTL_BOLD;
				m1 += " -" + m2;
				m1 += GCTL_RESET;
#endif
			}
			loop_index++;
		}
		else break;
	}
	m1 += extra_usage;

	display_line(front_space, back_space, 7, width, m1, sout);
	sout << std::endl;

	//找到最长的选项作为排版的依据
	int temp_len, remain_len, max_opt_length = -1;
	loop_index = 0;
	while(1)
	{
		if (longopts[loop_index].name != 0)
		{
			temp_len = strlen(longopts[loop_index].name);
			max_opt_length = GCTL_MAX(max_opt_length, temp_len + 1);
			loop_index++;
		}
		else break;
	}

	m1 = "Options:";
	display_line(front_space, back_space, 0, width, m1, sout);

	// display usage
	loop_index = 0;
	while(1)
	{
		if (longopts[loop_index].name != 0)
		{
			for (int j = 0; j < front_space+2; j++)
				sout << " ";

			temp_len = strlen(longopts[loop_index].name) + 1;
			remain_len = max_opt_length - temp_len;

			if (longopts[loop_index].val >= 65 && longopts[loop_index].val <= 122)
			{
				if (expd_longopts[loop_index].manda)
				{
#ifdef _WINDOWS
					sout << "-" << (char)longopts[loop_index].val
						<< "    --" << longopts[loop_index].name << "    ";
#else
					sout << GCTL_BOLD << "-" << (char)longopts[loop_index].val 
						<< "    --" << longopts[loop_index].name << GCTL_RESET << "    ";
#endif
				}
				else sout << "-" << (char)longopts[loop_index].val 
					<< "    --" << longopts[loop_index].name << "    ";
			}
			else
			{
				if (expd_longopts[loop_index].manda)
				{
#ifdef _WINDOWS
					sout << "      --" << longopts[loop_index].name << "    ";
#else
					sout << GCTL_BOLD << "      --" << longopts[loop_index].name << GCTL_RESET << "    ";
#endif
				}
				else sout << "      --" << longopts[loop_index].name << "    ";
			}

			for (int j = 0; j < remain_len; j++)
				sout << " ";

			ss_message.clear();
			ss_message.str(expd_longopts[loop_index].info);
			line_length = front_space + back_space + max_opt_length + 14;
			while(ss_message >> segment)
			{
				if ((line_length+segment.length()+1) <= width)
				{
					sout << segment << " ";
					line_length += (segment.length()+1);
				}
				else
				{
					sout << std::endl;
					for (int j = 0; j < front_space+max_opt_length+13; j++)
						sout << " ";
					sout << segment << " ";
					line_length = (segment.length()+13+max_opt_length+front_space+back_space);
				}
			}
			sout << std::endl;

			loop_index++;
		}
		else break;
	}
	sout << std::endl;

	m1 = "This page is generated by the GCTL library automatically.";
	display_line(front_space, back_space, 4, width, m1, sout);
	return;
}

/**
 * @brief      Display the help information of one option indicated by its id.
 *
 * @param[in]  val   The option's value
 * @param      exp_longopts  The expanded longopts
 */
void gctl::getopt_long_option_info(int val, const option *longopts, const option_info *expd_longopts, 
	std::ostream& sout)
{
	int width;
	//获取终端窗口的行列数
#ifdef _WINDOWS
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
	width = csbi.srWindow.Right - csbi.srWindow.Left;
#else
	struct winsize w;
	ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
	width = w.ws_col;
#endif

	std::string m1, m2;
	int loop_index = 0;
	while (1)
	{
		if (longopts[loop_index].name != 0)
		{
			if (longopts[loop_index].val == val)
			{
				m1 = "Option: -";
				m2 = (char)longopts[loop_index].val;
				m1 += m2;

				if (expd_longopts[loop_index].format != 0)
				{
					m2 = expd_longopts[loop_index].format;
					m1 += m2 + " ";
				}
				else m1 += " ";

				m2 = expd_longopts[loop_index].info;
				m1 += m2;
				display_line(0, 10, 4, width, m1, sout);
			}
			loop_index++;
		}
		else break;
	}
	return;
}

/**
 * 以下为类的方法定义
 */

gctl::flags_parser::flags_parser()
{
	configured_ = false;
	failed_mandatory_ = false;
	pro_name_ = pro_info_ = "";
	nofound_return_ = "NULL";
	noarg_return_ = "NO_ARG";
}

gctl::flags_parser::~flags_parser(){}

void gctl::flags_parser::add_opt(int s_name, const char* f_name, int has_arg, 
	int *flag, const char* info, const char* format, bool manda)
{
	option p = {f_name, has_arg, flag, s_name};
	option_info f = {info, format, manda};
	rec_opts_.push_back(p);
	rec_info_.push_back(f);
	configured_ = false;
	return;
}

void gctl::flags_parser::configure(int argc, char **argv)
{
	if (rec_opts_.empty())
		throw length_error("No option registered yet. From flags_parser::configure(...)");

	if (pro_name_ == "" || pro_info_ == "")
		throw invalid_argument("The program's name or description is not set. From flags_parser::configure(...)");

	// 拷贝命令行参数个数与地址
	argc_ = argc;
	argv_ = &argv[0];

	// 先在记录的option与option_info后添加一个全为0的对象
	option p = {0, 0, 0, 0};
	option_info f = {0, 0, 0};
	rec_opts_.push_back(p);
	rec_info_.push_back(f);

	// 解析参数符号串
	arg_format_ = "";
	// 最后一个参数是空的 不要
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		arg_format_ += (char) rec_opts_[i].val;
		if (rec_opts_[i].has_arg == 1) arg_format_ += ":";
		else if (rec_opts_[i].has_arg == 2) arg_format_ += "::";
	}

	configured_ = true;
	return;
}

bool gctl::flags_parser::pass_mandatory()
{
	return !failed_mandatory_;
}

int gctl::flags_parser::set_opt(const int s_name)
{
	std::string err_str;
	if (!configured_)
		throw runtime_error("flags_parser is not configured. From flags_parser::set_opt(...)");

	int is_set = 0;

	// 下面这段检查是针对程序编写中的可能出现的错误调用
	bool is_manda = false;
	bool registered = false;
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		if (s_name == rec_opts_[i].val)
		{
			registered = true;
			if (rec_info_[i].manda) is_manda = true;
			break;
		}
	}

	if (!registered)
	{
		err_str = "Option -";
		err_str += (char) s_name;
		err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::set_opt(...)";
		throw domain_error(err_str);;
	}

	optind = 0; // 重置已分析参数个数 再从头分析一遍
	while (1)
	{
		int optIndex = 0;
		int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex);
		if (curr == -1 && is_manda)
		{
			err_str = "Mandatory option -";
			err_str += (char) s_name;
			err_str += " is not found and returned -1. From flags_parser::set_opt().";
			GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
			show_option_info(s_name);
			is_set = -1;
			failed_mandatory_ = true;
			break;
		}
		else if (curr == -1) break;

		if (curr == s_name)
		{
			is_set = 1; break;
		}
		else if (curr == '?')
		{
			err_str = "Invalid option found and returned 0. From flags_parser::set_opt().";
			GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
			break;
		}
	}

	return is_set;
}

int gctl::flags_parser::set_opt(const char* f_name)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::set_opt(...)";
		throw err_str;
	}

	// 下面这段检查是针对程序编写中的可能出现的错误调用
	int s_name = '?';
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		if (!strcmp(f_name, rec_opts_[i].name))
		{
			s_name = rec_opts_[i].val;
			break;
		}
	}

	if (s_name == '?')
	{
		err_str = "Option --";
		err_str += f_name;
		err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::set_opt(...)";
		throw err_str;
	}

	return set_opt(s_name);
}

std::string gctl::flags_parser::get_arg(const int s_name)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::get_arg(...)";
		throw err_str;
	}

	std::string out_opt = nofound_return_;

	// 下面这段检查是针对程序编写中的可能出现的错误调用
	bool registered = false;
	bool no_arg = true;
	bool is_manda = false;
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		if (s_name == rec_opts_[i].val)
		{
			registered = true;
			if (rec_opts_[i].has_arg != 0) no_arg = false;
			if (rec_info_[i].manda) is_manda = true;
			break;
		}
	}

	if (!registered)
	{
		err_str = "Option -";
		err_str += (char) s_name;
		err_str += " is not registered. Use flags_parser::add_opt() to register before use. From flags_parser::get_arg(...)";
		throw err_str;
	}

	if (no_arg)
	{
		err_str = "Option -";
		err_str += (char) s_name;
		err_str += " has no argument. From flags_parser::get_arg(...)";
		throw err_str;
	}

	// 下面是检查命令行参数拾取中的可能出现的错误输入
	optind = 0; // 重置已分析参数个数 再从头分析一遍
	while (1)
	{
		int optIndex = 0;
		int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex);
		if (curr == -1 && is_manda)
		{
			err_str = "Mandatory option -";
			err_str += (char) s_name;
			err_str += " is not found and returned NULL. From flags_parser::get_arg(...)";
			GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
			show_option_info(s_name);
			failed_mandatory_ = true;
			break;
		}
		else if (curr == -1) break;

		if (curr == s_name)
		{
			/**
			 * 注意 如果s_name对应的选项的参数是可选的(optional_argument) 则其在调用get_arg()时可能没有参数
			 * 此时optarg2将指向一个空指针 不能进行赋值操作 最终的函数返回值为NULL 即没有拾取到参数
			 */
			if (optarg != NULL) out_opt = optarg;
			else out_opt = noarg_return_;
			break;
		}
		else if (curr == '?')
		{
			err_str = "Invalid option found and returned NULL. From flags_parser::get_arg(...)";
			GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
			break;
		}
	}

	return out_opt;
}

std::string gctl::flags_parser::get_arg(const char* f_name)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::get_arg(...)";
		throw err_str;
	}

	int s_name = '?';
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		if (!strcmp(f_name, rec_opts_[i].name))
		{
			s_name = rec_opts_[i].val;
			break;
		}
	}

	if (s_name == '?')
	{
		err_str = "Option --";
		err_str += f_name;
		err_str += " is not registered. From flags_parser::get_arg(...)";
		throw err_str;
	}

	return get_arg(s_name);
}

void gctl::flags_parser::get_argv(std::initializer_list<char> tar_val, std::initializer_list<std::string*> ret_str)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::get_argv(...)";
		throw err_str;
	}

	if (tar_val.size() != ret_str.size())
	{
		err_str = "target and result sizes don't match. From flags_parser::get_argv(...)";
		throw err_str;
	}

	// 下面这段检查是针对程序编写中的可能出现的错误调用
	bool registered, no_arg;
	std::initializer_list<char>::iterator icl;
	std::initializer_list<std::string*>::iterator isl;

	char s_name;
	for (icl = tar_val.begin(); icl != tar_val.end(); ++icl)
	{
		s_name = *icl;
		registered = false;
		no_arg = true;
		for (int i = 0; i < rec_opts_.size()-1; i++)
		{
			if (s_name == rec_opts_[i].val)
			{
				registered = true;
				if (rec_opts_[i].has_arg != 0) no_arg = false;
				break;
			}
		}

		if (!registered)
		{
			err_str = "Option -";
			err_str += (char) s_name;
			err_str += " is not registered. From flags_parser::get_argv(...)";
			throw err_str;
		}

		if (no_arg)
		{
			err_str = "Option -";
			err_str += (char) s_name;
			err_str += " has no argument. From flags_parser::get_argv(...)";
			throw err_str;
		}
	}

	for (isl = ret_str.begin(); isl != ret_str.end(); ++isl)
	{
		(*isl)->assign(nofound_return_);
	}

	// 下面是检查命令行参数拾取中的可能出现的错误输入
	bool is_manda, end_analysis = false;

	isl = ret_str.begin();
	for (icl = tar_val.begin(); icl != tar_val.end(); ++icl)
	{
		s_name = *icl;
		is_manda = false;
		for (int i = 0; i < rec_opts_.size()-1; i++)
		{
			if (s_name == rec_opts_[i].val)
			{
				if (rec_info_[i].manda) is_manda = true;
				break;
			}
		}

		optind = 0; // 重置已分析参数个数 再从头分析一遍
		while (1)
		{
			int optIndex = 0;
			int curr = getopt_long(argc_, argv_, arg_format_.c_str(), rec_opts_.data(), &optIndex);
			if (curr == -1 && is_manda)
			{
				err_str = "Mandatory option -";
				err_str += (char) s_name;
				err_str += " is not found and returned NULL. From flags_parser::get_argv(...)";
				GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
				show_option_info(s_name);
				failed_mandatory_ = true;
				break;
			}
			else if (curr == -1) break;

			if (curr == s_name)
			{
				/**
				 * 注意 如果s_name对应的选项的参数是可选的(optional_argument) 则其在调用get_arg()时可能没有参数
				 * 此时optarg2将指向一个空指针 不能进行赋值操作 最终的函数返回值为NULL 即没有拾取到参数
				 */
				if (optarg != NULL) (*isl)->assign(optarg);
				else (*isl)->assign(noarg_return_);
				break;
			}
			else if (curr == '?')
			{
				err_str = "Invalid option found and returned NULL. From flags_parser::get_argv(...)";
				GCTL_ShowWhatError(err_str, GCTL_ERROR_ERROR, 0, 0, 0);
				end_analysis = true;
				break;
			}
		}

		isl++;
		if (end_analysis) break;
	}

	return;
}

void gctl::flags_parser::show_help_page(std::ostream& sout, std::string extra_usage)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::show_help_page(...)";
		throw err_str;
	}

	gctl::display_logo(sout);
	gctl::getopt_long_help(rec_opts_.data(), rec_info_.data(), pro_name_.c_str(), pro_info_.c_str(), sout, extra_usage);
	return;
}

void gctl::flags_parser::show_option_info(int s_name, std::ostream& sout)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::show_option_info(...)";
		throw err_str;
	}

	gctl::getopt_long_option_info(s_name, rec_opts_.data(), rec_info_.data(), sout);
	return;
}

void gctl::flags_parser::show_option_info(const char* f_name, std::ostream& sout)
{
	std::string err_str;
	if (!configured_)
	{
		err_str = "flags_parser is not configured. From flags_parser::show_option_info(...)";
		throw err_str;
	}

	int s_name = '?';
	for (int i = 0; i < rec_opts_.size()-1; i++)
	{
		if (!strcmp(f_name, rec_opts_[i].name))
		{
			s_name = rec_opts_[i].val;
			break;
		}
	}

	if (s_name == '?')
	{
		err_str = "Option --";
		err_str += f_name;
		err_str += " is not registered. From flags_parser::show_option_info(...)";
		throw err_str;
	}

	gctl::getopt_long_option_info(s_name, rec_opts_.data(), rec_info_.data(), sout);
	return;
}

void gctl::flags_parser::set_proname(const char* proname)
{
	pro_name_ = proname;
	return;
}

void gctl::flags_parser::set_proinfo(const char* proinfo)
{
	pro_info_ = proinfo;
	return;
}

void gctl::flags_parser::set_nofound_return(std::string in_str)
{
	nofound_return_ = in_str;
	return;
}

void gctl::flags_parser::set_noarg_return(std::string in_str)
{
	noarg_return_ = in_str;
	return;
}

void gctl::flags_parser::show_arg_format(std::ostream& sout)
{
	sout << arg_format_ << std::endl;
	return;
}
